#include <glib.h>
#include <list>
#include <algorithm>
#include "GCritSecWrapper.h"
#include "GEventWrapper.h"
#include "GThreadPoolWrapper.h"

CGThreadPoolWrapper::CGThreadPoolWrapper(const gchar *name)
:m_threadPool(NULL)
{
  m_name = g_strdup(name);
}

CGThreadPoolWrapper::~CGThreadPoolWrapper()
{
  DestroyPool(FALSE, TRUE);

  /* Destroy processes */
  CGAutoLock lock(&m_threadsCS);

  for(GThreadPoolProcessIt it=m_submitThreads.begin(), end=m_submitThreads.end(); it!=end; ++it)
    delete (*it);

  for(GThreadPoolProcessIt it=m_pushedThreads.begin(), end=m_pushedThreads.end(); it!=end; ++it)
    delete (*it);

  for(GThreadPoolProcessIt it=m_doneThreads.begin(), end=m_doneThreads.end(); it!=end; ++it)
    delete (*it);

  for(GThreadPoolProcessIt it=m_runningThreads.begin(), end=m_runningThreads.end(); it!=end; ++it)
  {
    (*it)->StopProcess();
    delete (*it);
  }

  g_free(m_name);
}

gboolean CGThreadPoolWrapper::CreatePool(gint maxThreads, gboolean exclussive)
{
  if(!g_thread_supported())
    return FALSE;

  /* already created */
  if(m_threadPool)
    return FALSE;

  GError *error = NULL;
  m_threadPool = g_thread_pool_new(CGThreadPoolWrapper::ThreadPoolProc, this, maxThreads, exclussive, &error);
  if(!m_threadPool)
  {
    g_print("Error starting ThreadPool \"%s\" (%s)\n", m_name, error? error->message : NULL);
    g_error_free(error);
    return FALSE;
  }
  g_print("ThreadPool \"%s\" created\n", m_name);

  return TRUE;
}

/* immediate: No process pending processes in pool
   wait: wait processes running end */
gboolean CGThreadPoolWrapper::DestroyPool(gboolean immediate, gboolean wait)
{
  if(!m_threadPool)
    return FALSE;

  g_thread_pool_free(m_threadPool, immediate, wait);
  m_threadPool = NULL;

  /* move to done */
  CGAutoLock lock(&m_threadsCS);

  while(m_submitThreads.size()>0)
  {
    GThreadPoolProcessIt it = m_submitThreads.begin();
    CGThreadPoolProcess *process = *it;
	m_submitThreads.remove(process);
	m_doneThreads.push_back(process);
  }

  while(m_pushedThreads.size()>0)
  {
    GThreadPoolProcessIt it = m_pushedThreads.begin();
    CGThreadPoolProcess *process = *it;
	m_pushedThreads.remove(process);
	m_doneThreads.push_back(process);
  }

  while(m_runningThreads.size()>0)
  {
    GThreadPoolProcessIt it = m_runningThreads.begin();
    CGThreadPoolProcess *process = *it;
	m_runningThreads.remove(process);
	m_doneThreads.push_back(process);
  }

  g_print("ThreadPool \"%s\" destroyed\n", m_name);

  return TRUE;
}

void CGThreadPoolWrapper::ThreadPoolProc(gpointer userData, gpointer params)
{
  CGThreadPoolWrapper *threadPool = reinterpret_cast<CGThreadPoolWrapper *> (params);
  CGThreadPoolProcess *thread = reinterpret_cast<CGThreadPoolProcess *> (userData);

  while(1)
  {
    CGAutoLock lock(&threadPool->m_threadsCS);
    threadPool->m_pushedThreads.remove(thread);
    threadPool->m_runningThreads.push_back(thread);
    break;
  }

  /* event running */
  thread->m_eventRunning.Set();

  thread->m_running = TRUE;
  thread->RunProcess();
  
  /* FIXME: check already exist in running list. (DestroyPool with wait to false) */
  GThreadPoolProcessIt it  = std::find(threadPool->m_runningThreads.begin(), threadPool->m_runningThreads.end(), thread);
  if(it == threadPool->m_runningThreads.end())
  {
	g_print("Warning: %p doesn't exist. Previous pool destroyed with wait to false\n", thread);
    return;
  }

  thread->m_running = FALSE;

  /* event stoppped */
  thread->m_eventStopped.Set();

  while(1)
  {
    CGAutoLock lock(&threadPool->m_threadsCS);
    threadPool->m_runningThreads.remove(thread);
    threadPool->m_doneThreads.push_back(thread);
    break;
  }
}

gboolean CGThreadPoolWrapper::Submit(CGThreadPoolProcess *process, gboolean push)
{
  g_return_val_if_fail(process, FALSE);

  /* first add to submit */
  CGAutoLock lock(&m_threadsCS);
  m_submitThreads.push_back(process);

  gboolean ret = TRUE;
  if(push)
    ret = Push(process);
  /* if push fails process will remain in done list */
  if(!ret)
	m_doneThreads.remove(process);

  return ret;
}

gboolean CGThreadPoolWrapper::Push(CGThreadPoolProcess *process)
{
  g_return_val_if_fail(process, FALSE);

  CGAutoLock lock(&m_threadsCS);
 
  gboolean ret = TRUE;
  m_submitThreads.remove(process);
  m_pushedThreads.push_back(process);

  GError *error = NULL;
#if GLIB_CHECK_VERSION(2, 20, 1)
  g_thread_pool_push(m_threadPool, process, &error);
  if(error)
#else
  if(!g_thread_pool_push(m_threadPool, process, &error))
#endif
  {
    /* to done */
    m_pushedThreads.remove(process);
    m_doneThreads.push_back(process);

    g_print("Error pushing process in ThreadPool \"%s\" (%s)\n", m_name, error? error->message : NULL);
    g_error_free(error);
    ret = FALSE;
  }

  return ret;
}

gboolean CGThreadPoolWrapper::Push()
{
  /* submit top running */
  CGAutoLock lock(&m_threadsCS);

  while(m_submitThreads.size() > 0)
  {
    GThreadPoolProcessIt it = m_submitThreads.begin();
    CGThreadPoolProcess *process = *it;

	Push(process);
  }

  return TRUE;
}

gboolean CGThreadPoolWrapper::WaitRunningDone(gulong milis /*= WAIT_INFINITE*/)
{
  gboolean timeout = FALSE;
  gboolean emptyLists = FALSE;
  GTimer *timer = NULL;
  if(milis != WAIT_INFINITE)
  {
	timer = g_timer_new();
	g_timer_start(timer);
  }
  
  while(!emptyLists && !timeout)
  {
	if(milis != WAIT_INFINITE)
	  timeout = (g_timer_elapsed(timer, NULL) * 1000 >= milis);

	while(1)
	{
	  CGAutoLock lock(&m_threadsCS);
	  emptyLists = ((m_pushedThreads.size() == 0) && (m_runningThreads.size() == 0));

	  break;
	}

    g_thread_yield();
  }

  return !timeout;
}
